package kaginawa;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;

import netnotifier.NetNotifier;
import netnotifier.notification.NetNotification;
import netnotifier.notification.NetNotification;

import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.CTabItem;
import org.eclipse.swt.custom.StackLayout;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.ControlListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Sash;
import org.eclipse.swt.widgets.ScrollBar;
import org.eclipse.swt.widgets.TabItem;
import org.eclipse.swt.widgets.Table;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.TableItem;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeColumn;
//import org.eclipse.swt.widgets.Table;
//import org.eclipse.swt.widgets.TableColumn;
//import org.eclipse.swt.widgets.TableItem;
import org.eclipse.swt.widgets.TreeItem;

public class KaginawaMonitor
{
	// subclassing not allowed, so I will just wrap
	// the kaginawa monitor around TabItem

	CTabItem tabItem=null; // the tab

	Composite composite=null;     // contains the table and the tree
	Table notificationTable=null; // the table part of the monitor
	Tree  notificationTree=null;  // the tree part of the monitor
	
	ArrayList<Object> notificationList=new ArrayList<Object>(); // list of notifications
	
	String notificationClassName=null;
	String[] notificationFields=null; // a list of interesting fields of this class
	Class<?> notificationClass=null; // the class we are looking at
	ScrollBar verticalScrollBar=null; // the scroll bar on the table
	
	boolean scrolling=true; // should the table do live scrolling
	Sash sash=null; // used to adjust sizes
	
	
	public KaginawaMonitor(String classpath, String protocolName)
	{
		// make sure we know about this protocol	
		try
		{
			notificationClass=Class.forName(classpath+"."+protocolName);
		}
		catch(Exception e)
		{
			e.printStackTrace();
			return;
		}
		
		// get the field list for this protocol
		try
		{	
			notificationFields=(String[])notificationClass.getField("defaultFields").get(null);
		}
		catch(Exception e)
		{
			e.printStackTrace();
			System.out.println("could not find method");
		}
		
		// create the tab
		tabItem=new CTabItem(Kaginawa.tabFolder,SWT.NONE);
		Kaginawa.tabFolder.setSelection(tabItem);
		tabItem.setText(protocolName+" Monitor");
		composite=new Composite(Kaginawa.tabFolder,SWT.NONE);
		composite.setLayout(new GridLayout(1,true));
		
		// make a table to monitor notifications
		notificationTable=new Table(composite,SWT.VIRTUAL|SWT.BORDER|SWT.MULTI);
		notificationTable.setLayoutData(new GridData(GridData.FILL_BOTH|GridData.GRAB_HORIZONTAL));
		notificationTable.setHeaderVisible(true);
		notificationTable.setLinesVisible(true);
		notificationTable.pack();
		
		// get a handle on the scrollbar
		verticalScrollBar=notificationTable.getVerticalBar();

		/*
		sash=new Sash(composite,SWT.HORIZONTAL);
		sash.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));
		sash.addSelectionListener(new SelectionAdapter()
		{
			public void widgetSelected(SelectionEvent event)
			{
				System.out.println("amazing "+event);
				sash.getParent().layout();
			}
		});
		*/
		
		//*
		// make a tree that will show more details about a notification
		notificationTree=new Tree(composite,SWT.BORDER);
		GridData layoutData=new GridData(GridData.FILL_HORIZONTAL);
		//layoutData.heightHint=0;
		notificationTree.setLayoutData(layoutData);
		//notificationTree.setHeight(100);
		
		//*
		
		//notificationTree.pack();
		//notificationTree.setVisible(false);
		
		//notificationTree.setHeight(100);
		
		//*/
		
		composite.layout();
		tabItem.setControl(composite);
		
		// set the column names for the table
		for(String field:notificationFields)
		{
			TableColumn column=new TableColumn(notificationTable,SWT.NONE);
			//column.setWidth(160);
			column.setText(field);	
			column.pack();
		}
		new TableColumn(notificationTable,SWT.NONE).pack();		
		
		//// ******    add listeners     *********
		
		// add selection listener to the scroll bar object
		verticalScrollBar.addSelectionListener(new SelectionListener()
		{
			public void widgetDefaultSelected(SelectionEvent event)
			{				
			}

			public void widgetSelected(SelectionEvent event)
			{
				// listen for drag events in the scrollbar
				// if the user scrolls away from the bottom, stop auto scrolling
				// if the user scrolls back to the bottom, resume auto scrolling
				if(event.detail!=SWT.DRAG)return;
				
				if(verticalScrollBar.getSelection() + verticalScrollBar.getThumb() > verticalScrollBar.getMaximum() - 1)
				{
					scrolling=true;
				}
				else scrolling=false;
			}
		});

		// add the virtual table listener
		// this is the scrolling table that tracks notifications that are being monitored
		//
		// SWT virtual tables are nifty.  They only care about the object being displayed
		// when you want to look at it.  When you are not looking at it, it throws away
		// the TableItem, and it only allocates TableItems when you scroll to look at them.
		// This is the event that is called when it detects you are trying to look at a
		// TableItem that isn't there, so it populates the TableItem with information in the
		// notificationList, which gets appended in the NetNotifier callback.
		notificationTable.addListener(SWT.SetData, new Listener()
		{
			public void handleEvent (Event event) // called whenever the table needs to display a new item
			{
				try
				{
					TableItem item = (TableItem) event.item;
					int index = notificationTable.indexOf(item);
					NetNotification notification=(NetNotification)notificationList.get(index);
					String fieldValues[]=new String[notificationFields.length];
					for(int x=0;x<notificationFields.length;++x)
					{
						Object fieldValue=notificationClass.getField(notificationFields[x]).get(notification);
						if(fieldValue!=null)fieldValues[x]=fieldValue.toString();
						else fieldValues[x]="null";
					}
					item.setText(fieldValues);
					item.setData(notification);

					// make sure all the collumns are still the right width
					for(TableColumn column:notificationTable.getColumns())
					{
						column.pack();
					}
					
					// if the scrollbar is at the bottom, reactivate scrolling
					if(verticalScrollBar.getSelection() + verticalScrollBar.getThumb() == verticalScrollBar.getMaximum() - 1)
					{
						scrolling=true;
					}
						
					// scroll to the bottom immediately after the next SWT action happens
					// the next action should be the addition of the item to the table
					if(scrolling)Kaginawa.display.asyncExec(new Runnable()
					{
						public void run()
						{
							try
							{
								verticalScrollBar.setSelection(verticalScrollBar.getMaximum());
							}
							catch(Exception e)
							{
							}
						}
					});
				}
				catch(Exception e)
				{
					e.printStackTrace();
					return;
				}
			}
		});
		
		
		
		notificationTable.addSelectionListener(new SelectionListener()
		{
			// called when a table item is clicked
			public void widgetSelected(SelectionEvent event)
			{
				scrolling=false;
			}
		
			// a table item has been double clicked
			// display details in the tree
			public void widgetDefaultSelected(SelectionEvent event)
			{
				if(!(event.item instanceof TableItem))return;
				TableItem item=(TableItem)event.item;
				NetNotification notification=(NetNotification)item.getData();
				
				// delete all the items in the tree
				//notificationTree.clearAll(true);
				notificationTree.removeAll();
				
				TreeItem structureItem=new TreeItem(notificationTree,SWT.NULL);
				structureItem.setText("structure");
				structureItem.setExpanded(true);
				
				//for(Class<?> subClass:notification.getClass().getClasses())

				for(Class<?> currentClass=notification.getClass();
					!currentClass.getSimpleName().equals("NetNotification");
					currentClass=currentClass.getSuperclass())
				{
					TreeItem currentClassItem=new TreeItem(structureItem,SWT.NULL);
					currentClassItem.setText(currentClass.getSimpleName());
					
					for(Field field:currentClass.getDeclaredFields())	
					{
						String fieldName=field.getName();
						String fieldValue=null;
						try
						{
							Object fieldObject=field.get(notification);
							if(fieldObject==null)fieldValue="null";
							else fieldValue=fieldObject.toString();
						}
						catch (Exception e)
						{
							System.out.println("(1)couln't access "+field.getName());
							//e.printStackTrace();
							continue; // go to the next iteration in the loop
						}
						
						TreeItem fieldItem=new TreeItem(currentClassItem,SWT.NULL);
						fieldItem.setText(fieldName+": "+fieldValue);
					}
				}
	
				
				TreeItem componentsItem=new TreeItem(notificationTree,SWT.NULL);
				componentsItem.setText("components");
				componentsItem.setExpanded(true);
				for(Object component:notification.components.toArray())
				{
					TreeItem componentItem=new TreeItem(componentsItem,SWT.NULL);
					componentItem.setText(component.getClass().getSimpleName());
					
					//for(Field field:component.getClass().getDeclaredFields())	
					for(Field field:component.getClass().getFields())
					{
						String fieldName=field.getName();
						String fieldValue=null;
						try
						{
							Object fieldObject=field.get(notification);
							if(fieldObject==null)fieldValue="null";
							else fieldValue=fieldObject.toString();
						}
						catch (Exception e)
						{
							e.printStackTrace();
							System.out.println("(2)couln't access "+field.getName());
							//e.printStackTrace();
							continue; // go to the next iteration in the loop
						}
						
						TreeItem fieldItem=new TreeItem(componentItem,SWT.NULL);
						fieldItem.setText(fieldName+": "+fieldValue);
					}
				}

				TreeItem contributionsItem=new TreeItem(notificationTree,SWT.NULL);
				contributionsItem.setText("contributions");
				contributionsItem.setExpanded(true);
				for(Object contribution:notification.contributions.toArray())
				{
					TreeItem contributionItem=new TreeItem(contributionsItem,SWT.NULL);
					contributionItem.setText(contribution.getClass().getSimpleName());
					
					//for(Field field:contribution.getClass().getDeclaredFields())
					for(Field field:contribution.getClass().getFields())
					{
						String fieldName=field.getName();
						String fieldValue=null;
						try
						{
							Object fieldObject=field.get(notification);
							if(fieldObject==null)fieldValue="null";
							else fieldValue=fieldObject.toString();
						}
						catch (Exception e)
						{
							System.out.println("(3)couln't access "+field.getName());
							//e.printStackTrace();
							continue; // go to the next iteration in the loop
						}
						
						TreeItem fieldItem=new TreeItem(contributionItem,SWT.NULL);
						fieldItem.setText(fieldName+": "+fieldValue);
					}
				}
				
				/*
				TreeItem item1=new TreeItem(notificationTree,SWT.NULL);
				item1.setText("item1");
				item1.setExpanded(true);
				TreeItem item2=new TreeItem(item1,SWT.NULL);
				item2.setText("item2");
				item2.setExpanded(true);
				TreeItem item3=new TreeItem(item2,SWT.NULL);
				item3.setText("item3");
				notificationTree.setSize(0,0);
				*/
					
					
				// expand or collapse TreeItem
				notificationTree.getParent().layout();
			}
		});
		
		notificationTree.addSelectionListener(new SelectionListener()
		{
			// called on click
			public void widgetSelected(SelectionEvent event)
			{
			}
		
			// called on double click
			public void widgetDefaultSelected(SelectionEvent event)
			{
				if(!(event.item instanceof TreeItem))return;
				TreeItem item=(TreeItem)event.item;
				
				// expand or collapse TreeItem
				item.setExpanded(!item.getExpanded());
				notificationTree.getParent().layout();
			}
		});
		
		//*/
		
		// subscribe to the notifier
		Kaginawa.notifier.subscribe(notificationClass,this,"notified");
	}
	
	// this is the method that gets called whenever an object we care about
	// enters the notifier
	public void notified(Object notification)
	{
		notificationList.add(notification);
		//System.out.println("got a notification");
		
		if(Kaginawa.display.isDisposed())return;
		Kaginawa.display.syncExec(new Runnable()
		{
			public void run()
			{
				try
				{
					notificationTable.setItemCount(notificationList.size());
					notificationTable.clearAll();
				}
				catch(Exception e)
				{
					// this is thrown whenever the monitor is disposed while
					// it is trying to add something to the list, which is
					// pretty much every time a monitor is disposed
					
					//e.printStackTrace();
				}
			}
		});
	}
}
